package com.dbflow5.migration;

import com.dbflow5.StringUtils;
import com.dbflow5.config.FlowManager;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.FlowCursor;
import com.dbflow5.query.Select;
import com.dbflow5.sql.SQLiteType;
import java.util.*;

import com.dbflow5.annotation.CallSuper;

/**
 * Description: Provides a very nice way to alter a single table quickly and easily.
 */
public class AlterTableMigration<T> extends BaseMigration {
    private static final String ALTER_TABLE = "ALTER TABLE ";

    private final Class<T> table;

    /**
     * The query to rename the table with
     */
    private String renameQuery = null;

    /**
     * The columns to ALTER within a table
     */
    private final List<String> internalColumnDefinitions = new ArrayList<>();

    private final List<String> columnNames = new ArrayList<>();

    /**
     * The old name of the table before renaming it
     */
    private String oldTableName = null;

    public AlterTableMigration(Class<T> table){
        this.table = table;
    }

    @Override
    public void migrate(DatabaseWrapper database) {
        // "ALTER TABLE "
        String sql = ALTER_TABLE;
        String tableName = FlowManager.getTableName(table);

        // "{oldName}  RENAME TO {newName}"
        // Since the structure has been updated already, the manager knows only the new name.
        StringBuilder buildString = new StringBuilder();
        buildString.append(sql);
        StringUtils.appendQuotedIfNeeded(buildString, oldTableName);
        buildString.append(renameQuery);
        buildString.append(tableName);
        database.execSQL(buildString.toString());

        // We have column definitions to add here
        // ADD COLUMN columnName {type}
        if (!internalColumnDefinitions.isEmpty()) {
            FlowCursor cursor = Select.select().from(table).limit(0).cursor(database);
            sql = sql + tableName;
            for (int index = 0; index < internalColumnDefinitions.size(); index++) {
                String columnDefinition = internalColumnDefinitions.get(index);
                String columnName = StringUtils.stripQuotes(columnNames.get(index));
                if (cursor.getColumnIndexForName(columnName) == -1) {
                    database.execSQL(sql+" ADD COLUMN "+columnDefinition);
                }
            }
        }
    }

    @CallSuper
    public void onPostMigrate() {
        // cleanup and make fields eligible for garbage collection
        renameQuery = null;
        internalColumnDefinitions.clear();
        columnNames.clear();
    }

    /**
     * Call this to rename a table to a new name.
     *
     * @param oldName The new name to call the table.
     * @return This instance
     */
    public AlterTableMigration<T> renameFrom(String oldName) {
        oldTableName = oldName;
        renameQuery = " RENAME TO ";
        return this;
    }

    /**
     * Add a column to the DB. This does not necessarily need to be reflected in the [T],
     * but it is recommended.
     *
     * @param sqLiteType The type of column represented in the DB.
     * @param columnName The name of the column to add. Use the "_Table" class for the specified table.
     * @param defaultValue the default value as a String representation.
     * Leaving parameter as null leaves the case out.
     * For NULL column add defaultValue = "NULL". Encapsulate the value in quotes "\'name\'" if it's a string.
     * @return This instance
     */
    public AlterTableMigration<T> addColumn(SQLiteType sqLiteType, String columnName, String defaultValue) {
        String addStatement = StringUtils.quoteIfNeeded(columnName) + " " + sqLiteType.name();
        if (defaultValue != null) {
            addStatement += " DEFAULT " + defaultValue;
        }
        internalColumnDefinitions.add(addStatement);
        columnNames.add(columnName);
        return this;
    }

    /**
     * Add a column to the DB. This does not necessarily need to be reflected in the [T],
     * but it is recommended.
     *
     * @param sqLiteType      The type of column that pertains to an [SQLiteType]
     * @param columnName      The name of the column to add. Use the "$Table" class for the specified table.
     * @param referenceClause The clause of the references that this foreign key points to.
     * @return This instance
     */
    public AlterTableMigration<T> addForeignKeyColumn(SQLiteType sqLiteType, String columnName, String referenceClause) {
        internalColumnDefinitions.add(StringUtils.quoteIfNeeded(columnName) + " " + sqLiteType.name() + " REFERENCES " + referenceClause);
        columnNames.add(columnName);
        return this;
    }

    /**
     * getRenameQuery
     *
     * @return The query that renames the table.
     */
    public String getRenameQuery() {
        StringBuilder builder = new StringBuilder();
        builder.append(ALTER_TABLE);
        StringUtils.appendQuotedIfNeeded(builder, oldTableName);
        builder.append(renameQuery);
        builder.append(FlowManager.getTableName(table));
        return builder.toString();
    }

    /**
     * getColumnDefinitions
     *
     * @return A List of column definitions that add op to a table in the DB.
     */
    public List<String> getColumnDefinitions() {
        String sql = ALTER_TABLE + " " + FlowManager.getTableName(table);
        List<String> newListString = new ArrayList<>();
        List<String> listString = internalColumnDefinitions;
        for(String columns : listString){
            listString.add(sql + " ADD COLUMN " + columns);
        }
        return newListString;
    }

}
